#include "TablaHash.h"

TablaHash* crearTabla(TamBucket tamanioBucket)
{
	TablaHash* tabla = (TablaHash*)malloc(sizeof(TablaHash));
	tabla->tamanioBucket = tamanioBucket;
	tabla->tamanioTabla = 0;
	tabla->buffer = (Buffer *)malloc(sizeof(Buffer) * CANT_BUFFERS_TABLA); //la tabla puede almacenar 2 bloques
	tabla->buffer[0].Buckets = NULL;
	tabla->buffer[1].Buckets = NULL;
	tabla->buffer[0].lru = TRUE;
	tabla->buffer[1].lru = FALSE;
	tabla->buffer[0].nroBloque = -1;
	tabla->buffer[1].nroBloque = -1;
	tabla->cantBuckets = 0;
	return tabla;
}

unsigned int func_hash(TClave clave)
{
    unsigned hash = 0;
    int i;

    unsigned int clave_long = strlen(clave);

    for (i = 0; i < clave_long; i++) {
        hash += clave[i];
        hash += (hash << 10);
        hash ^= (hash >> 6);
    }
    hash += (hash << 3);
    hash ^= (hash >> 11);
    hash += (hash << 15);

    return hash;
}

unsigned int IndiceEnTabla (TClave clave, unsigned int tamanioTabla)
{
	unsigned int clave_hash = func_hash(clave);

	return clave_hash % tamanioTabla;
}

int getIndiceBufferByNroBloque (FILE * archTabla, TablaHash * tabla, unsigned int nroBloque)
{
	if (tabla->buffer[0].nroBloque == nroBloque)
	{
		setIndiceBufferLRU(tabla, 0);
		return 0;
	}
	if (tabla->buffer[1].nroBloque == nroBloque)
	{
		setIndiceBufferLRU(tabla, 1);
		return 1;
	}
	int error = LevantarBloqueTabla(archTabla, tabla, nroBloque);
	if (error == OK)
	{
		//Se devuelve el que se levanto recien
		return getIndiceBufferLRU(tabla, FALSE);
	}
	return ERROR; //-1
}


int GuardarBucketEnTabla (FILE* archTabla, TablaHash* tabla, unsigned int nroBucket, unsigned int dispersion, int indiceTabla)
{
	int i, cantIndices;
	int indiceTablaActual = indiceTabla;
	cantIndices = tabla->tamanioTabla / dispersion;
	int cantBloquesTabla = getCantBloquesTabla(*tabla);

	if (cantBloquesTabla > 1)
	{
		int nroBloque = getNroBloqueByIndiceTabla(indiceTablaActual);
		int indiceBufferAnterior = getIndiceBufferByNroBloque(archTabla, tabla, nroBloque);
		for (i = 0; i < cantIndices; i++)
		{
			nroBloque = getNroBloqueByIndiceTabla(indiceTablaActual);
			int indiceBuffer = getIndiceBufferByNroBloque(archTabla, tabla, nroBloque);
			int cantBuckets = LONG_BUFFER_TABLA / sizeof(unsigned int);
			int indice = indiceTablaActual % cantBuckets;

			tabla->buffer[indiceBuffer].Buckets[indice] = nroBucket;

			indiceTablaActual = (indiceTablaActual + dispersion) % tabla->tamanioTabla;

			if (indiceBufferAnterior != indiceBuffer)
			{
				//guardo el bloque que ya fue actualizado
				int error = GuardarBloqueTabla(archTabla, tabla, indiceBufferAnterior);
				if (error == ERROR)
					return error;

				if ((i+1) == cantIndices)
				{
					int error = GuardarBloqueTabla(archTabla, tabla, indiceBuffer);
					if (error == ERROR)
						return error;
				}
				else{
					indiceBufferAnterior = indiceBuffer;
				}
			}

			if (((i+1) == cantIndices) && (indiceBufferAnterior == indiceBuffer))
			{
				//guardo el ultimo buffer usado
				int error = GuardarBloqueTabla(archTabla, tabla, indiceBuffer);
				if (error == ERROR)
					return error;
			}
		}
	}
	else
	{
		int indiceBuffer = getIndiceBufferByNroBloque(archTabla, tabla, 0); //existe solo el bloque 0
		for (i = 0; i < cantIndices; i++)
		{
			tabla->buffer[indiceBuffer].Buckets[indiceTablaActual] = nroBucket;
			indiceTablaActual = (indiceTablaActual + dispersion) % tabla->tamanioTabla;
		}
		int error = GuardarBloqueTabla(archTabla, tabla, indiceBuffer);
		return error;
	}
	return OK;
}

int DuplicarTabla (FILE* archTabla, TablaHash* tabla)
{
	unsigned int tamanioOrig = tabla->tamanioTabla;
	//Modifico el tamaño de la tabla.
	int cantBloquesTablaOrig = getCantBloquesTabla(*tabla);
	tabla->tamanioTabla = tamanioOrig * 2;
	int cantBloquesTablaNuevo = getCantBloquesTabla(*tabla);

	//int longitud = sizeof (TamBucket);
	//int cantBuckets = LONG_BUFFER_TABLA / longitud; //siempre da entero

	if (cantBloquesTablaNuevo > cantBloquesTablaOrig)
	{
		int nroBloque;
		for (nroBloque = 0; nroBloque < cantBloquesTablaOrig; nroBloque++)
		{
			//Levanto a memoria y escribo copiado a disco
			int indice = getIndiceBufferByNroBloque(archTabla, tabla, nroBloque);

			if (indice == ERROR)
				return ERROR;

			//Me muevo una distancia igual al tamaño original de la tabla y copio un bloque
			tabla->buffer[indice].nroBloque = cantBloquesTablaOrig + nroBloque;
			int error = GuardarBloqueTabla(archTabla, tabla, indice);
			if (error == ERROR)
				return ERROR;
		}
	}
	else
	{
		//Obtengo el buffer que se esta usando, el único, porque hay un único bloque
		int indice = getIndiceBufferByNroBloque(archTabla, tabla, 0);

		if (indice == ERROR)
			return ERROR;

		// Duplico el tamanio de la tabla.
		tabla->buffer[indice].Buckets = (unsigned int*) realloc(tabla->buffer[indice].Buckets, sizeof(unsigned int) * (tamanioOrig*2));

		//Copio los valores de la tabla en igual sentido en la segunda mitad de la tabla.
		int i,j;
		for(i = tamanioOrig,j=0; i < (2*tamanioOrig); i++,j++) {
			tabla->buffer[indice].Buckets[i] = tabla->buffer[indice].Buckets[j];
		}
		int error = GuardarBloqueTabla(archTabla, tabla, indice);
		if (error == ERROR)
			return ERROR;
	}

	GuardarMetaData(archTabla, tabla);

	return OK;
}

int ReducirTabla (TablaHash* tabla, FILE * archTabla)
{
	if (tabla->tamanioTabla < 1)
		return ERROR;

	int error;

	//Tabla se queda vacia
	if (tabla->tamanioTabla == 1)
	{
		tabla->tamanioTabla--;

		if (tabla->buffer[0].Buckets)
		{
			free(tabla->buffer[0].Buckets); //libero el espacio reservado anterior
			tabla->buffer[0].Buckets = NULL;
			tabla->buffer[0].nroBloque = -1;
		}

		if (tabla->buffer[1].Buckets)
		{
			free(tabla->buffer[1].Buckets); //libero el espacio reservado anterior
			tabla->buffer[1].Buckets = NULL;
			tabla->buffer[1].nroBloque = -1;
		}

		error = GuardarMetaData(archTabla, tabla);
		return error;
	}

	unsigned int longitud = sizeof (unsigned int);
	unsigned int i;
	unsigned int dispersion = tabla->tamanioTabla / 2;
	int cantBloquesTablaOrig = getCantBloquesTabla(*tabla);
	tabla->tamanioTabla = dispersion;
	int cantBloquesTablaNuevo = getCantBloquesTabla(*tabla);


	if (cantBloquesTablaNuevo < cantBloquesTablaOrig)
	{
		int cantBuckets = LONG_BUFFER_TABLA / longitud; //siempre da entero
		int nroBloque;

		for (nroBloque = 0; nroBloque < cantBloquesTablaNuevo; nroBloque++)
		{
			int indiceBuffer1 = getIndiceBufferByNroBloque(archTabla, tabla, nroBloque);
			if (indiceBuffer1 == ERROR)
				return ERROR;

			int indiceBuffer2 = getIndiceBufferByNroBloque(archTabla, tabla, nroBloque + cantBloquesTablaNuevo);
			if (indiceBuffer2 == ERROR)
				return ERROR;

			for (i = 0; i < cantBuckets; i++)
			{
				if (tabla->buffer[indiceBuffer1].Buckets[i] != tabla->buffer[indiceBuffer2].Buckets[i])
				{
					//recupero el estado de la tabla
					tabla->tamanioTabla = tabla->tamanioTabla * 2;
					return ERROR;
				}
			}
		}
	}
	else
	{
		//Obtengo el buffer que se esta usando, el único, porque hay un único bloque
		int indice = getIndiceBufferByNroBloque(archTabla, tabla, 0);
		if (indice == ERROR)
			return ERROR;

		for (i = 0; i < dispersion; i++)
		{
			if (tabla->buffer[indice].Buckets[i] != tabla->buffer[indice].Buckets[i+dispersion])
			{
				//recupero el estado de la tabla
				tabla->tamanioTabla = tabla->tamanioTabla * 2;
				return ERROR;
			}
		}
		unsigned int * BloquesAux = (unsigned int *) malloc (sizeof (unsigned int) * dispersion);
		for (i = 0; i < dispersion; i++)
		{
			BloquesAux[i] = tabla->buffer[indice].Buckets[i];
		}
		free (tabla->buffer[indice].Buckets);
		tabla->buffer[indice].Buckets = BloquesAux;
	}

	error = GuardarMetaData(archTabla, tabla);

	return error;
}


void BloqueTablaABytes(TablaHash* tabla, char* bloqueAPersistir, int indice)
{
	//[Bucket i][Bucket i+1]...[Bucket i+CantBucketsPorBloque]

	int longitud = sizeof(TamBucket);
	int actual = 0;
	int cantBuckets = LONG_BUFFER_TABLA / longitud; //siempre da entero

	int i;
	for(i = 0; i < cantBuckets; i++)
	{
		if (i == tabla->tamanioTabla) break; // Corte, en caso que el bloque no este lleno

		memcpy(&bloqueAPersistir[actual], &tabla->buffer[indice].Buckets[i], longitud);
		actual += longitud;
	}

	memset(&bloqueAPersistir[actual],' ',LONG_BUFFER_TABLA - actual);
}


void MetaDataABytes(TablaHash* tabla, char* metadata)
{
	//[tamanioTabla][tamanioBucket][cantBuckets]

	int longitud = sizeof(unsigned int);
	int actual = 0;
	memcpy(&metadata[actual], &tabla->tamanioTabla, longitud);
	actual += longitud;

	memcpy(&metadata[actual], &tabla->tamanioBucket, longitud);
	actual += longitud;

	memcpy(&metadata[actual], &tabla->cantBuckets, longitud);
	actual += longitud;

	memset(&metadata[actual],' ',LONG_METADATA - actual);
}


int getCantBloquesTabla (TablaHash tabla)
{
	div_t bloques;
	bloques = div((tabla.tamanioTabla * sizeof(unsigned int)), LONG_BUFFER_TABLA); // DIV
	if ((tabla.tamanioTabla > 0) && (bloques.rem > 0))
		return (bloques.quot + 1); // Correccion si algun bloque esta ocupado, pero no lleno.
	return bloques.quot; // El ultimo bloque esta lleno
}

int LevantarMetaData (FILE * archTabla, TablaHash * tabla)
{
	unsigned int longitud = sizeof(TamBucket);
	unsigned int actual=0;

	char* metadata = (char*) malloc(sizeof(char) * LONG_METADATA);

	fseek (archTabla, 0, SEEK_SET);

	if (fread (metadata, LONG_METADATA, 1, archTabla) == 1) //valida que haya leido correctamente (que el archivo no este vacio)
	{
		memcpy(&tabla->tamanioTabla, &metadata[actual], longitud);
		actual += longitud;

		memcpy(&tabla->tamanioBucket, &metadata[actual], longitud);
		actual += longitud;

		memcpy(&tabla->cantBuckets, &metadata[actual], longitud);
		actual += longitud;
		free (metadata);

		return OK;
	}
	else
	{
		free (metadata);
		//Se inicializa el archivo
		return GuardarMetaData(archTabla, tabla);
	}
}


int GuardarMetaData (FILE* archTabla, TablaHash* tabla)
{
	char* metadata = (char*) malloc(sizeof(char) * LONG_METADATA);

	MetaDataABytes(tabla, metadata);

	fseek(archTabla, 0, SEEK_SET);

	int verif = fwrite(metadata, LONG_METADATA, 1, archTabla);
	free(metadata);
	if(verif == 1)
	{
		fflush(archTabla);
		return OK;
	}
	return ERROR;
}



unsigned int getNroBloqueByIndiceTabla(unsigned int indiceTabla)
{
	div_t nroBloque;
	nroBloque = div((indiceTabla * sizeof(unsigned int)), LONG_BUFFER_TABLA); // DIV
	return nroBloque.quot;
}

int getIndiceBufferLRU (TablaHash * tabla, enum TBOOL isLru)
{
	int indice;
	if (tabla->buffer[0].lru == isLru) //politica LRU
		indice = 0;
	else
		indice = 1;
	return indice;
}

//indice se acabo de usar
void setIndiceBufferLRU (TablaHash * tabla, int indice)
{
	//hay 2 elementos nada mas
	tabla->buffer[indice].lru = FALSE;
	int otro = (indice + 1) % CANT_BUFFERS_TABLA;
	tabla->buffer[otro].lru = TRUE;
}

int GuardarBloqueTabla (FILE* archTabla, TablaHash* tabla, int indiceBuffer)
{
	char * bloqueAPersistir = (char*) malloc(sizeof(char) * LONG_BUFFER_TABLA);
	BloqueTablaABytes(tabla, bloqueAPersistir, indiceBuffer);

	// Offset para el bloque a guardar
	int offset = LONG_METADATA + tabla->buffer[indiceBuffer].nroBloque * LONG_BUFFER_TABLA;
	fseek(archTabla, offset, SEEK_SET);

	int verif = fwrite(bloqueAPersistir, LONG_BUFFER_TABLA, 1, archTabla);
	free(bloqueAPersistir);
	if(verif == 1)
	{
		fflush(archTabla);
	}
	else return ERROR;

	return OK;
}


int LevantarBloqueTabla (FILE* archTabla, TablaHash * tabla, unsigned int nroBloque)
{
	//[tamanioTabla][tamanioBucket][cantBuckets][Bucket1][Bucket2]...[BucketN]
	//Un bloque de tabla contiene muchos Nros de Buckets
	//Si la tabla es pequeña, el primer bloque puede no estar lleno.
	//Si la tabla es grande y ocupa mas de un bloque, todos los bloques 1,2,3,4,etc... estaran siempre llenos.

	unsigned int longitud = sizeof(unsigned int);
	unsigned int cantNrosBucketPorBloque = LONG_BUFFER_TABLA / longitud;
	unsigned int actual=0;
	int i;
	char* bloqueTabla;
	// 'i' se usa para el vector de BloquesTabla, 'j' se usa para el vector de Buckets

	int indice = getIndiceBufferLRU(tabla, TRUE);
	setIndiceBufferLRU(tabla, indice); //LRU

	int offset = LONG_METADATA + nroBloque * LONG_BUFFER_TABLA;
	fseek(archTabla, offset, SEEK_SET);

	bloqueTabla = (char*) malloc(sizeof(char) * LONG_BUFFER_TABLA);

	if (fread (bloqueTabla, LONG_BUFFER_TABLA, 1, archTabla) == 1) //valida que haya leido correctamente (que el archivo no este vacio)
	{
		if (tabla->buffer[indice].Buckets)
		{
			free(tabla->buffer[indice].Buckets); //libero el espacio reservado anterior
			tabla->buffer[indice].Buckets = NULL;
		}

		tabla->buffer[indice].nroBloque = nroBloque;
		if (tabla->tamanioTabla < cantNrosBucketPorBloque)
			tabla->buffer[indice].Buckets = (unsigned int *) malloc(tabla->tamanioTabla * sizeof(unsigned int));
		else
			tabla->buffer[indice].Buckets = (unsigned int *) malloc(cantNrosBucketPorBloque * sizeof(unsigned int));
		actual = 0;

		for (i = 0; i < cantNrosBucketPorBloque; i++)
		{
			if (i == tabla->tamanioTabla) break; // No hay mas Nros de Buckets para Levantar
			memcpy (&tabla->buffer[indice].Buckets[i], &bloqueTabla[actual], longitud);
			actual += longitud;
		}
	}
	else
	{
		free (bloqueTabla);
		return ERROR;
	}
	free (bloqueTabla);
	return OK;
}

void visualizarTabla(FILE* archTabla, TablaHash* tabla, FILE* salida)
{
	int i;
	int cantBloquesTabla = getCantBloquesTabla(*tabla);

	int cantBuckets = LONG_BUFFER_TABLA / sizeof (unsigned int); //siempre da entero

	fprintf(salida,"Tabla: ");


	for (i = 0; i<cantBloquesTabla; i++)
	{
		int indiceBuffer = getIndiceBufferByNroBloque(archTabla, tabla, i);
		int j;

		for (j=0; j < cantBuckets ;j++){
			if (j == tabla->tamanioTabla)
				break;
			fprintf(salida,"%d ",tabla->buffer[indiceBuffer].Buckets[j]);
		}

	}

	fprintf(salida,"\nTamanio: %d\n", tabla->tamanioTabla);
}
